package com.github.baichuan.iot.gateway.handler;

import com.github.baichuan.iot.gateway.Network;
import com.github.baichuan.iot.gateway.context.DeviceContext;
import com.github.baichuan.iot.gateway.context.DeviceContextCache;
import com.github.baichuan.iot.protocol.codec.MessageEncodeContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.ReferenceCountUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;

/**
 * 报文出站处理器
 * 通过Channel将DatagramContext中的datagram发送给终端，当发送失败时记录日志
 */
@Slf4j
@ChannelHandler.Sharable
public class OutboundMessageHandler extends ChannelOutboundHandlerAdapter {
    private Network network;

    private DeviceContextCache cache;

    public OutboundMessageHandler(@NonNull Network network, @NonNull DeviceContextCache cache){
        this.network = network;
        this.cache = cache;
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        MessageEncodeContext outbound = (MessageEncodeContext) msg;
        String device = outbound.getDevice();

        DeviceContext deviceContext = cache.get(device);
        if(deviceContext.isDebug()){
            log.info("[DEBUG:{}] send datagram {} outbound.",
                    device,
                    ByteBufUtil.hexDump(outbound.getDatagram()));
        }

        promise.addListener(future -> {
            if(future.isDone()){
                if(!future.isSuccess()){
                    Throwable cause = future.cause();
                    log.warn("{} outbound datagram is not transmitted because {}", device, cause.getMessage());

                    Channel channel = ctx.channel();
                    if(!channel.isWritable()){
                        log.warn("{} channel is not writable", device);
                        cache.evict(device);
                    }

                } else {
                    if(deviceContext.isDebug()){
                        log.info("[DEBUG:{}] send outbound datagram 0x{} is success",
                                device, Integer.toHexString(outbound.getId()));
                    }
                }
            }
        });

        ByteBuf buf = Unpooled.wrappedBuffer(outbound.getDatagram());
        switch (network){
            case TCP:
                super.write(ctx, buf, promise);
                break;
            case UDP:
                if(cache.contains(device)){
                    DeviceContext context = cache.get(device);
                    DatagramPacket datagramPacket = new DatagramPacket(buf, (InetSocketAddress) context.getRecipient());
                    super.write(ctx, datagramPacket, promise);
                }
                break;
        }
        super.flush(ctx);
        ReferenceCountUtil.release(buf);
    }
}
